﻿using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Threading;
using System.IO;
using System.Reflection;

namespace DbView
{
	/*
		New for 2012 Change log
		- 10 Jan 2012: Changes to apply a resource pattern
	*/
	static class Program
	{
		// Source for the application environment.
		const string XML_CONFIG_FILE = "DbViewData4.xml";
		const string TEMPLATE_FILE = "DbViewTemplates4.txt";
		const string LOOKUP_CONFIG_FILE = "DbViewLookups.xml";
		const string DBVIEW_FOLDER_NAME = "DbView";  // named as such for legacy reasons.
		const string DBVIEW_DOC_FOLDER_NAME = "Documentation";  // This must match the installer program
		const string PLUGIN_INTERFACE = "DbViewDriver.IDataSource"; // interface name to search for in assemblies.
		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]
		static void Main(string[] args)
		{
			InitialiseEnvironment();

			string arg = "";  // connection tag
			if (args.GetLength(0) > 0) arg = args[0];

			// replaced by plugins
			// LoadResources();
			Application.EnableVisualStyles();
			Application.SetCompatibleTextRenderingDefault(false);

			// Add the event handler for handling UI thread exceptions to the event.
			Application.ThreadException += new ThreadExceptionEventHandler(Program.Form1_UIThreadException);
			// Set the unhandled exception mode to force all Windows Forms errors to go through our handler.
			Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
			string configErrMessage = "";
			try
			{
				// make
				//String appFolder = "DbView"; // selected for legacy naming reasons.
				//String dataPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), appFolder);
				string dataPath = GetConfigFilePath();
				// Before any file work check for version change and move/copy files if necessary
				DoUpgradeCheck(dataPath);
				// Initialiase Singletons from config: Connections, View defs, Templates?, Preferences?
				InitialiseResources(dataPath);
				// Load drivers
				string PluginInterface = "DbViewDriver.IDataSource";
				PluginLoader.LoadPlugins(PluginInterface);
				foreach (object plugin in PluginManager.Instance.GetPlugins(PluginInterface))
				{
					DbViewDriver.IDataSource iplug = plugin as DbViewDriver.IDataSource;
					if (iplug.PerformDriverCheck(out configErrMessage) == false)
					{
						// dump to console for increased visibility
						Console.WriteLine(configErrMessage);
						// add fluff and display to the user.
						configErrMessage = string.Format(@"
A driver has failed to load properly and is unlikely to work. Please check 
the message below for instructions on how to fix this or how to remove the 
component if you do not wish to use it

{0}", configErrMessage);
						MessageBox.Show(configErrMessage, "Driver Check Failed", MessageBoxButtons.OK, MessageBoxIcon.Warning);
					}
					else
						Console.WriteLine("!!! Loaded: {0}", iplug.DriverName);
				}
			}
			catch (FileNotFoundException fnfExc)
			{
				configErrMessage = string.Format(@"
A driver has failed to load properly and is unlikely to work. Please check 
the message below to determine which driver it is likely to be. Check the
DbViewSharp website for what to install if you wish to use it or how to remove
the component if you do not wish to use it.

{0}", fnfExc.Message);
				MessageBox.Show(configErrMessage, "Driver Failed to Load", MessageBoxButtons.OK, MessageBoxIcon.Warning);
			}
			catch (Exception exc)
			{
				MessageBox.Show(exc.Message, "Startup Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);
			}
			// create the main form with the application presenter object
			MainForm mainFrame = new MainForm(arg);

			// because the VS for designer keeps shrinking the window I'm hard-coding it from here
			SetMainWindowSize(mainFrame);

			// off you go then.
			Application.Run(mainFrame);

			ReleasePlugins();
		}

		/*
			Register files and paths for the application from the constants at the top of this source module
			They should be available to all.
		*/
		private static void InitialiseEnvironment()
		{
			ApplicationEnvironment.ConfigFileName = XML_CONFIG_FILE;
			ApplicationEnvironment.TemplateFileName = TEMPLATE_FILE;

			// define a special folder in the application data folder just for DbViewSharp
			string appDataRoot = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData);
			string dataPath = Path.Combine(appDataRoot, DBVIEW_FOLDER_NAME);
			ApplicationEnvironment.DataFolder = dataPath;

			// figure out where the program is running once and log it here
			Assembly assem = Assembly.GetExecutingAssembly();
			string exeFile = assem.Location; // exe name
			ApplicationEnvironment.ProgramFolder = Path.GetDirectoryName(exeFile);  // path element
			ApplicationEnvironment.ProgramName = Path.GetFileName(exeFile);

			// Documents go in a folder under the exe path named Documentation
			ApplicationEnvironment.DocumentFolder = Path.Combine(ApplicationEnvironment.ProgramFolder, DBVIEW_DOC_FOLDER_NAME);
		}

		private static void SetMainWindowSize(Form mainForm)
		{
			mainForm.Height = 700;
			mainForm.Width = 1000;
		}

		// Load Resources (previously in model code).
		// Note: If we ever re-implement the watcher options for multiple instances 
		// then this will need to move somewhere else;
		private static void InitialiseResources(string ConfigFilePath)
		{
			string operation = "";
			try
			{
				// The config file is the base for the other resources.
				operation = "Load new config from " + XML_CONFIG_FILE;
				string xmlConfigFile = Path.Combine(ConfigFilePath, XML_CONFIG_FILE);
				DbView.Resources.ConfigDoc.Instance.LoadConfig(xmlConfigFile);

				operation = "Load connections from " + XML_CONFIG_FILE;;
				DbView.Resources.ConnectionListMgr.Instance.Load();

				operation = "Load settings from"  + XML_CONFIG_FILE;;
				DbView.Resources.SettingsMgr.Instance.Load();

				operation = "Init Favourites";
				// TODO: Decide once and for all. Will there be favourites. I don't think so
				// InitFavourites();
				operation = "Load Lookups from " + LOOKUP_CONFIG_FILE;
				string lookupConfigFile = Path.Combine(ConfigFilePath, LOOKUP_CONFIG_FILE);
				DbView.Resources.LookupInfo.Instance.LoadConfig(lookupConfigFile);

			}
			catch (Exception exc)
			{
				// below should quit forthwith
				StartupErrorExit(operation, exc.Message);
			}
		}

		private static string GetConfigFilePath()
		{
			Assembly assem = Assembly.GetExecutingAssembly();
			string exeFile = assem.Location; // exe name
			string exePath = Path.GetDirectoryName(exeFile);  // path element
			string portableConfigFile = Path.Combine(exePath, XML_CONFIG_FILE);
			if (File.Exists(portableConfigFile))
			{
				DbView.Resources.ConfigDoc.Instance.PortableMode = true; // set portable mode.
				return exePath;
			}
			// make
			string appFolder = "DbView"; // selected for legacy naming reasons.
			string dataPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), appFolder);
			return dataPath;
		}

		private static void ReleasePlugins()
		{
			List<object> drivers = PluginManager.Instance.GetPlugins(PLUGIN_INTERFACE);
			foreach (object o in drivers)
			{
				DbViewDriver.IDataSource driver = o as DbViewDriver.IDataSource;
				if (driver != null)
					driver.Dispose();
			}

		}

		private static void StartupErrorExit(string errorOp, string exceptionMessage)
		{
				string template = @"
What's Happened? 
There's been a start-up error.
The most likely cause is a corrupted Xml configuration file. 
The most important file is {0} as it contains 
your connection information. 

So what now?
It's not a difficult file to edit so please try yourself. 
An explorer window should be displaying the file after 
this application closes. To help you the failing operation was
[  {1}  ]
and the error was
[  {2}  ]

I don't get it or it's still not working?
Then I should move the broken file in the configuration
folder to a hold folder and restart DbViewSharp. It does
not require the files to be present to work. You will need 
to add your connection info again though. 
  Once the system is working again though you can
try to manually add back the data from the old file
piece by piece. * NOTE: Exit DBViewSharp before making
manual edits as the running program will overwrite
changes.

Any other advice?
Other files contain less important information which 
you can probably get by without. Follow the 
move / add back gradually strategy for these as well.
For now however the system is unable to continue until 
you take some steps to repair it.

Good Luck!";

			string message = string.Format(template, XML_CONFIG_FILE, errorOp, exceptionMessage);
			MessageBox.Show(message, "DbViewSharp: Fatal Error on Start-up", MessageBoxButtons.OK, MessageBoxIcon.Stop);
			// System.Diagnostics.Debug.WriteLine(String.Format("Reload error: {0}\n {1}", operation, exc.Message));
			{
				// ask user if they want to open the folder and do it
				string exeFile = "explorer.exe";
				string exeParams = Path.GetDirectoryName(DbView.Resources.ConfigDoc.Instance.FileName);
				ProcessRunner pr = new ProcessRunner(exeFile);
				pr.ShellExec(exeParams);
				if (pr.LastProcessErrored)
				{
					message = string.Format("The command:\n {0} {1}\n caused the following error:\n{2}", exeFile, exeParams, pr.Output);
					MessageBox.Show(message, "Oh no. More trouble", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
				}
			}
			Environment.Exit(1);
		}

		/*
			This because I followed some guidelines where to store application-related data.
			Turns out that when you change version the data goes in a new folder with the 
			new version name. So rather than change everything around the app will make the
			check and copy below at start-up.
			It relies on me only ever releasing certain versions into the wild. All the known
			versions I ever release will be listed here and queried in prefered order for 
			the connection information

			2012: Taking the step of moving the location 
			In the new scheme the path sent in will not be the same root as the old path.
			The known versions only apply to Application.UserAppDataPath so we'll wire it in
			here.
		*/
		private static void DoUpgradeCheck(String ConfigFilePath)
		{
			// actually lets just hard-code the versions we know about
			String[] knownVersions = new String[] {
				"1.2.0.0", // entry 0 is the current version 
				"1.1.0.0", 
				"1.0.0.0"
				};
			// trap a very common programmer error; ie. not updating that horrid hard-code list above.
			// Future developers/me. You'll get this if you set the version to increase automatically 
			// with each build.
			//			if (ConfigFilePath.IndexOf(knownVersions[0]) == -1)
			//				throw new ApplicationException("DoUpgradeCheck: current version missing in knownVersions");

			string xmlConfigFile = Path.Combine(ConfigFilePath, XML_CONFIG_FILE);
			string templateFile = Path.Combine(ConfigFilePath, TEMPLATE_FILE);
			// do nothing if a file exists in the current folder
			// 9999/10,000 you are here.
			if (File.Exists(xmlConfigFile)) return;

			string legacyPath = Application.UserAppDataPath;
			// replace whatever the build is with the last legacy location
			int iPos = legacyPath.LastIndexOf("\\");
			legacyPath = legacyPath.Substring(0, iPos + 1) + knownVersions[0];

			// here either if new or in an upgrade situation
			foreach (String s in knownVersions)
			{
				// ok so the first one is redundant as we already know there are no files.
				string testPath = legacyPath.Replace(knownVersions[0], s);
				string sourceFile = testPath + @"\" + XML_CONFIG_FILE;
				if (File.Exists(sourceFile))
				{
					// config file: connections and views
					File.Copy(sourceFile, xmlConfigFile);
					// snippet file. (May not be there though).
					sourceFile = testPath + @"\" + TEMPLATE_FILE;
					if (File.Exists(sourceFile)) File.Copy(sourceFile, templateFile);
					// job done
					return;
				}
			}
		}

		// Handle the UI exceptions by showing a dialog box, and asking the user whether
		// or not they wish to abort execution.
		private static void Form1_UIThreadException(object sender, ThreadExceptionEventArgs t)
		{
			DialogResult result = DialogResult.Cancel;
			try
			{
				result = ShowThreadExceptionDialog("Windows Forms Error", t.Exception);
			}
			catch
			{
				try
				{
					MessageBox.Show("Fatal Windows Forms Error",
						"Fatal Windows Forms Error", MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Stop);
				}
				finally
				{
					Application.Exit();
				}
			}

			// Exits the program when the user clicks Abort.
			if (result == DialogResult.Abort)
				Application.Exit();
		}
		// Creates the error message and displays it.
		private static DialogResult ShowThreadExceptionDialog(string title, Exception e)
		{
			string errorMsg = "An application error occurred. Please contact the project developer " +
				"with the following information:\n\n";
			errorMsg = errorMsg + e.Message;
#if DEBUG
			errorMsg = errorMsg + e.Message + "\n\nStack Trace:\n" + e.StackTrace;
#endif
			return MessageBox.Show(errorMsg, title, MessageBoxButtons.AbortRetryIgnore,
				MessageBoxIcon.Stop);
		}
	}

	static class ApplicationEnvironment
	{
		public static string DataFolder { get; set; }
		public static string ConfigFileName { get; set; }
		public static string TemplateFileName { get; set; }
		public static string ProgramFolder { get; set; }
		public static string ProgramName { get; set; }
		public static string DocumentFolder { get; set; }
	}

}
